package yartiss.engine.simulation.scheduling.policies.tools;

import java.util.Arrays;
import java.util.Comparator;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.SortedSet;

import yartiss.engine.simulation.IEnergyProfile;
import yartiss.engine.simulation.system.ITask;
import yartiss.engine.simulation.system.ITaskSet;
import yartiss.engine.simulation.system.SystemMetrics;

public class FPSlackEnergyManager3 {

	private static class Job {

		Job(ITask t, long x) {
			this.task = t;
			this.release = x;
			this.remainingCost = task.getWcet();
		}

		ITask task;
		long release;
		long remainingCost;

		@Override
		public String toString() {
			return "("+release + "," + task.getPriority() +"," + remainingCost + ")";
		}

	}

	/**
	 * 
	 * @param date
	 * @param taskSet
	 * @param energyProfile
	 * @return -1 if there is no slack energy, Emax+1 if Emax will be reached,
	 *         or an indicative value either
	 */
	public long getSlackEnergy(long date, ITaskSet taskSet, IEnergyProfile energyProfile) {

		debug = (date == -1) ? true : false;
		StringBuilder log=null;
		if(debug){
			log=new StringBuilder();
		}
		
		
		ITask tauk = taskSet.getActiveTasks(date).first();

		long dk = SystemMetrics.effectiveDeadline(tauk, taskSet, tauk.getNextAbsoluteDeadline(date));

		long t = date;
		SortedSet<ITask> headSet = taskSet.getHeadSet(tauk, false);

		// should be a stack, but most conveniently implemented like this for
		// sorted insertion of a new job at its release date
		Queue<Job> preemptedJobs = new PriorityQueue<FPSlackEnergyManager3.Job>(taskSet.getAllTasksCount(), new Comparator<Job>() {
			@Override
			public int compare(Job t1, Job t2) {
				long val = t1.task.getPriority() - t2.task.getPriority();
				if (val == 0)
					val = t2.release - t1.release;
				return (int) val;
			}
		});

		Job cj = new Job(tauk, t);
		cj.remainingCost=1;
		
		long e = energyProfile.getCurrentEnergyLevel();

		Queue<Job> activatedJobs = getActivatedJobs(date, dk, headSet, energyProfile);

		while (t < dk) {
			
			long dt = activatedJobs.isEmpty() ? dk - t : activatedJobs.peek().release - t;

			if(debug)
				log.append("t=").append(t).append(", dt = ").append(dt);
			
			if (dt > 0) { // on est pas arrive a la prochaine activation ni a dk
				
				if (cj != null) {// un job est actif
					
					if(debug)
						log.append(" job actif : ").append(cj);
					
					if (cj.remainingCost <= dt) {// le job est execute en entier
						e -= cj.remainingCost * cj.task.getEnergyConsumptionRate();
						e += cj.remainingCost * energyProfile.getPowerRate();
						t = t + cj.remainingCost;
						cj = preemptedJobs.poll();// may be null
						
						if(debug)
							log.append(" => on l'execute en entier");
						

					} else {// le job est execute pendant dt puis peut etre
							// preempte
						e -= dt * cj.task.getEnergyConsumptionRate();
						e += dt * energyProfile.getPowerRate();
						t = t + dt;
						cj.remainingCost -= dt;
						
						if(debug)
							log.append(" => on l'execute partiellement");
						
					}

				} else {// aucun job actif
					e += dt * energyProfile.getPowerRate();
					t = t + dt;
					
					if(debug)
						log.append("job actif : aucun !");
					
				}

			} else {// on est arrive a la prochaine activation ou a dk

				
				
				
				Job nj = activatedJobs.poll();
				
				if(debug)
					log.append(" on est arrive a l'activation de ").append(nj);
				
				if (cj == null) {// pas de job
					cj = nj;
				} else if (nj != null) {// un job en attente
					if (nj.task.getPriority() < cj.task.getPriority()) {// preemption
						preemptedJobs.add(cj);
						cj = nj;
					} else
						preemptedJobs.add(nj);
				}

			}
			
			if(debug)
				log.append(" ===> e = ").append(e).append("\n");
			
			if (e < 0)
				return -1;
			if (e >= energyProfile.getMaxEnergyLevel())
				return energyProfile.getMaxEnergyLevel() + 1;
		}
		
		if(debug)
			System.err.println(log);
		
		return e;

	}

	/**
	 * Construct and return a list containing the activated Jobs of taskSet's
	 * tasks between t1 and t2 (exclusive) ordered by activation date or
	 * priority in case of equality
	 * 
	 * @param t1
	 * @param t2
	 * @param taskSet
	 * @param energyProfile
	 * @return
	 */
	private Queue<Job> getActivatedJobs(long t1, long t2, SortedSet<ITask> taskSet, IEnergyProfile energyProfile) {

	

		if(debug){
			
			System.out.println("["+t1 +","+t2+"] "+Arrays.toString(taskSet.toArray()));
			
		}
		
		PriorityQueue<Job> queue = new PriorityQueue<FPSlackEnergyManager3.Job>(taskSet.size() + 1, new Comparator<Job>() {
			@Override
			public int compare(Job t1, Job t2) {
				long val = t1.release - t2.release;
				if (val == 0)
					val = t1.task.getPriority() - t2.task.getPriority();
				return (int) val;
			}
		});

		for (ITask task : taskSet) {

			long t = t1;
			while (t < t2) {

				long x = task.getNextActivation(t);
				if (x < t2)
					queue.add(new Job(task, x));
				t = x;

			}

		}

		
		 if(debug){
		 System.out.println("** : "+Arrays.toString(queue.toArray()));
		 }

		return queue;
	}

	boolean debug = false;

}
